import type { Prisma } from "@prisma/client";
import { Roles } from "@prisma/client";
import { useAtomValue } from "jotai";
import { ScanBarcodeIcon } from "lucide-react";
import type {
  LinksFunction,
  LoaderFunctionArgs,
  MetaFunction,
} from "react-router";
import {
  data,
  redirect,
  Link,
  NavLink,
  Outlet,
  useLoaderData,
} from "react-router";
import { ClientOnly } from "remix-utils/client-only";
import { AtomsResetHandler } from "~/atoms/atoms-reset-handler";
import { switchingWorkspaceAtom } from "~/atoms/switching-workspace";
import { ErrorContent } from "~/components/errors";

import {
  CommandPaletteButton,
  CommandPaletteRoot,
} from "~/components/layout/command-palette";
import { InstallPwaPromptModal } from "~/components/layout/install-pwa-prompt-modal";
import AppSidebar from "~/components/layout/sidebar/app-sidebar";
import {
  SidebarInset,
  SidebarProvider,
  SidebarTrigger,
} from "~/components/layout/sidebar/sidebar";
import { SkipLinks } from "~/components/layout/skip-links";
import { useCrisp } from "~/components/marketing/crisp";
import { ShelfMobileLogo } from "~/components/marketing/logos";
import { SequentialIdMigrationModal } from "~/components/sequential-id-migration-modal";
import { Spinner } from "~/components/shared/spinner";
import { Toaster } from "~/components/shared/toast";
import { NoSubscription } from "~/components/subscription/no-subscription";
import { config } from "~/config/shelf.config";
import { getBookingSettingsForOrganization } from "~/modules/booking-settings/service.server";
import { getSelectedOrganisation } from "~/modules/organization/context.server";
import { getUnreadCountForUser } from "~/modules/update/service.server";
import { getUserByID } from "~/modules/user/service.server";
import styles from "~/styles/layout/index.css?url";
import { appendToMetaTitle } from "~/utils/append-to-meta-title";
import {
  installPwaPromptCookie,
  initializePerPageCookieOnLayout,
  setCookie,
  userPrefs,
} from "~/utils/cookies.server";
import { isLikeShelfError, makeShelfError, ShelfError } from "~/utils/error";
import { isRouteError } from "~/utils/http";
import { payload, error } from "~/utils/http.server";
import type { CustomerWithSubscriptions } from "~/utils/stripe.server";

import {
  disabledTeamOrg,
  getCustomerActiveSubscription,
  getStripeCustomer,
  stripe,
  validateSubscriptionIsActive,
} from "~/utils/stripe.server";
import { canUseBookings } from "~/utils/subscription.server";
import { tw } from "~/utils/tw";

export const links: LinksFunction = () => [{ rel: "stylesheet", href: styles }];

export type LayoutLoaderResponse = typeof loader;

export async function loader({ context, request }: LoaderFunctionArgs) {
  const authSession = context.getSession();
  const { userId } = authSession;

  try {
    const user = await getUserByID(userId, {
      select: {
        id: true,
        email: true,
        username: true,
        firstName: true,
        lastName: true,
        profilePicture: true,
        onboarded: true,
        customerId: true,
        skipSubscriptionCheck: true,
        sso: true,
        tierId: true,
        roles: { select: { id: true, name: true } },
        userOrganizations: {
          where: {
            userId: authSession.userId,
          },
          select: {
            id: true,
            roles: true,
            organization: { select: { id: true } },
          },
        },
      } satisfies Prisma.UserSelect,
    });

    let subscription = null;

    if (user.customerId && stripe) {
      // Get the Stripe customer
      const customer = (await getStripeCustomer(
        user.customerId
      )) as CustomerWithSubscriptions;
      /** Find the active subscription for the Stripe customer */
      subscription = getCustomerActiveSubscription({ customer });
      await validateSubscriptionIsActive({ user, subscription });
    }

    /** This checks if the perPage value in the user-prefs cookie exists. If it doesnt it sets it to the default value of 20 */
    const userPrefsCookie = await initializePerPageCookieOnLayout(request);

    const cookieHeader = request.headers.get("Cookie");
    const pwaPromptCookie =
      (await installPwaPromptCookie.parse(cookieHeader)) || {};

    if (!user.onboarded) {
      return redirect("onboarding");
    }

    /** There could be a case when you get removed from an organization while browsing it.
     * In this case what we do is we set the current organization to the first one in the list
     */
    const { organizationId, organizations, currentOrganization } =
      await getSelectedOrganisation({ userId: authSession.userId, request });
    const isAdmin = user?.roles.some((role) => role.name === Roles["ADMIN"]);

    // Get current user's organization role for updates filtering
    const currentOrganizationUserRoles = user?.userOrganizations.find(
      (userOrg) => userOrg.organization.id === organizationId
    )?.roles;

    // Check if current user has OWNER or ADMIN role in the organization
    const isOwner = currentOrganizationUserRoles?.includes("OWNER");
    const isOrgAdmin = currentOrganizationUserRoles?.includes("ADMIN");

    // Check if sequential ID migration is needed
    const needsSequentialIdMigration =
      (isOwner || isOrgAdmin) && !currentOrganization.hasSequentialIdsMigrated;

    // Get unread updates count for the current user (using first organization role)
    const unreadUpdatesCount = currentOrganizationUserRoles?.[0]
      ? await getUnreadCountForUser({
          userId: authSession.userId,
          userRole: currentOrganizationUserRoles[0],
        })
      : 0;

    if (!organizations.length || !currentOrganization) {
      throw new ShelfError({
        cause: null,
        title: "No organization",
        message:
          "You are not part of any organization. Please contact support.",
        status: 403,
        label: "Organization",
      });
    }

    return data(
      payload({
        user,
        organizations,
        currentOrganizationId: organizationId,
        bookingSettings: await getBookingSettingsForOrganization(
          currentOrganization.id
        ),
        currentOrganization,
        currentOrganizationUserRoles,
        subscription,
        enablePremium: config.enablePremiumFeatures,
        hideNoticeCard: userPrefsCookie.hideNoticeCard,
        minimizedSidebar: userPrefsCookie.minimizedSidebar,
        hideInstallPwaPrompt: pwaPromptCookie.hidden,
        isAdmin,
        canUseBookings: canUseBookings(currentOrganization),
        unreadUpdatesCount,
        needsSequentialIdMigration,
        /** THis is used to disable team organizations when the currentOrg is Team and no subscription is present  */
        disabledTeamOrg: isAdmin
          ? false
          : currentOrganization.workspaceDisabled ||
            (await disabledTeamOrg({
              currentOrganization,
              organizations,
              url: request.url,
            })),
      }),
      {
        headers: [setCookie(await userPrefs.serialize(userPrefsCookie))],
      }
    );
  } catch (cause) {
    const reason = makeShelfError(cause, { userId: authSession.userId });
    throw data(error(reason), { status: reason.status });
  }
}

export const meta: MetaFunction<typeof loader> = ({ error }) => {
  if (!error) {
    return [{ title: "" }];
  }

  let title = "Something went wrong";

  if (isRouteError(error)) {
    title = error.data.error?.title ?? "";
  } else if (isLikeShelfError(error)) {
    title = error?.title ?? "";
  } else if (error instanceof Error) {
    title = error.name;
  }

  return [
    /** This will make sure that if we have an error its visible in the title of the browser tab */
    { title: appendToMetaTitle(title) },
  ];
};

export default function App() {
  useCrisp();
  const {
    disabledTeamOrg,
    minimizedSidebar,
    needsSequentialIdMigration,
    currentOrganizationId,
  } = useLoaderData<typeof loader>();
  const workspaceSwitching = useAtomValue(switchingWorkspaceAtom);

  const renderInstallPwaPromptOnMobile = () =>
    // returns InstallPwaPromptModal if the device width is lesser than 640px and the app is being accessed from browser not PWA
    window.matchMedia("(max-width: 640px)").matches &&
    !window.matchMedia("(display-mode: standalone)").matches ? (
      <InstallPwaPromptModal />
    ) : null;

  return (
    <CommandPaletteRoot>
      <SidebarProvider defaultOpen={!minimizedSidebar}>
        <SkipLinks />
        <AtomsResetHandler />
        <AppSidebar id="navigation" />
        <SidebarInset id="main-content" tabIndex={-1}>
          {disabledTeamOrg ? (
            <NoSubscription />
          ) : workspaceSwitching ? (
            <div className="flex size-full flex-col items-center justify-center text-center">
              <Spinner />
              <p className="mt-2">Activating workspace...</p>
            </div>
          ) : (
            <>
              <header className="flex items-center justify-between border-b bg-white py-4 md:hidden">
                <Link to="." title="Home" className="block h-8">
                  <ShelfMobileLogo />
                </Link>
                <div className="flex items-center space-x-2">
                  <CommandPaletteButton variant="icon" />
                  <NavLink
                    to="/scanner"
                    title="Scan QR Code"
                    className={({ isActive }) =>
                      tw(
                        "relative flex items-center justify-center px-2 transition",
                        isActive ? "text-primary-600" : "text-gray-500"
                      )
                    }
                  >
                    <ScanBarcodeIcon />
                  </NavLink>
                  <SidebarTrigger />
                </div>
              </header>
              <Outlet />
            </>
          )}
          <Toaster />
          <ClientOnly fallback={null}>
            {renderInstallPwaPromptOnMobile}
          </ClientOnly>

          {/* Sequential ID Migration Modal */}
          {needsSequentialIdMigration ? (
            <SequentialIdMigrationModal
              organizationId={currentOrganizationId}
            />
          ) : null}
        </SidebarInset>
      </SidebarProvider>
    </CommandPaletteRoot>
  );
}

export const ErrorBoundary = () => <ErrorContent />;
